#include <iostream>
#include <string>
#include <string.h>
#include "utils.h"

#include <gtk/gtk.h>
#include <vector>

using namespace std;

const std::string PROTOCOL("protocol");
const std::string MESSAGE("message");
const std::string INVITE("invite");                                       
const std::string ACCEPT("accept");
const std::string DENY("deny");
const std::string SYNC("sync");
const std::string OK("ok");


/*
 * Utilities implementation
 */

string string4xmlnode( xmlnode *x )
{
	std::cerr << "<begin> string --> xmlnode " << std::endl;
	string result;

	xmlnode *x2 = xmlnode_copy( x );

	char *str;
	int str_len;
	str = xmlnode_to_str( x2, &str_len );

	if( str != NULL ) {
		result = str;
	}

	if ( x != NULL && str == NULL ){
		std::cerr << "except: node dump to null string!" << std::endl;
	}

	xmlnode_free( x2 );
	g_free( str );

	std::cerr << "<end> string --> xmlnode " << std::endl;

	return result;
}

xmlnode* xmlnode4string( const string &s )
{
	std::cerr << "<begin> xmlnode --> string " << std::endl;

	xmlnode *result = NULL;

	char *str = new char [s.size()+1];                                           
	strcpy( str, s.c_str() );

	result = xmlnode_from_str( str, -1 ); 

	if ( s.size() > 0 && result == NULL ){
		std::cerr << "except: string parsing to null node!" << std::endl;
		std::cerr << "source string is: " << std::endl << s << std::endl;
	}

	std::cerr << "<end> xmlnode --> string " << std::endl;

	return result;
}



// =======================================
// C++Wrapper around popen
// -----------------------
//   Alex Vinokur
//     mailto:alexvn@go.to
//     http://go.to/alexvn
// -----------------------
//   2002-10-22
// =======================================


// ==============================================
// Windows 2000 Professional
// MinGW 2.0.0.-2
// gcc/g++ version 3.2 (mingw special 20020817-1)
// ==============================================


// ================
#include <stdio.h>
#include <assert.h>
#include <errno.h>

#include <iostream>
#include <string>
#include <vector>

using namespace std;

// =====================================
#define FATAL_MSG(msg) \
cout << msg \
<< " : " \
<< (strerror (errno)) \
<< " [ " \
<< __FILE__ \
<< ", #" \
<< __LINE__ \
<< " ]" \
<< endl

#define ERROR_MSG(msg) \
cout << msg \
<< " : " \
<< " [ " \
<< __FILE__ \
<< ", #" \
<< __LINE__ \
<< " ]" \
<< endl


// =====================================
bool popen_cxx ( const string&   command_i, vector<string>& result_o)
{
	bool            ret_boolValue = true;
	FILE*           fp;
	const int       SIZEBUF = 1234;
	char            buf [SIZEBUF];

	// ================================
	result_o = vector<string> ();
	// ================================

	if ((fp = popen(command_i.c_str (), "r")) == NULL)
	{
		FATAL_MSG ("Files or processes cannot be created");
		ret_boolValue = false;
		return ret_boolValue;
	}

	// ================================
	string  cur_string;
	while (fgets(buf, sizeof (buf), fp))
	{
		cur_string = buf;
		if (cur_string [cur_string.size () - 1] != '\n')
		{
			ERROR_MSG ("SIZEBUF too small (" << SIZEBUF << ")");
			ret_boolValue = false;
			return ret_boolValue;
		}
		assert (cur_string [cur_string.size () - 1] == '\n');
		result_o.push_back (cur_string.substr (0, cur_string.size () - 1));
	}

	// ================================
	if (pclose(fp) == -1)
	{
		FATAL_MSG ("Cannot execute pclose");
		ret_boolValue = false;
	}

	return ret_boolValue;

} // bool popen_cxx (...)


/* 
   base64.cpp and base64.h

   Copyright (C) 2004-2008 René Nyffenegger

   This source code is provided 'as-is', without any express or implied
   warranty. In no event will the author be held liable for any damages
   arising from the use of this software.

   Permission is granted to anyone to use this software for any purpose,
   including commercial applications, and to alter it and redistribute it
   freely, subject to the following restrictions:

   1. The origin of this source code must not be misrepresented; you must not
      claim that you wrote the original source code. If you use this source code
      in a product, an acknowledgment in the product documentation would be
      appreciated but is not required.

   2. Altered source versions must be plainly marked as such, and must not be
      misrepresented as being the original source code.

   3. This notice may not be removed or altered from any source distribution.

   René Nyffenegger rene.nyffenegger@adp-gmbh.ch

*/


static const std::string base64_chars = 
             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
             "abcdefghijklmnopqrstuvwxyz"
             "0123456789+/";


static inline bool is_base64(unsigned char c) {
  return (isalnum(c) || (c == '+') || (c == '/'));
}

std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
  std::string ret;
  int i = 0;
  int j = 0;
  unsigned char char_array_3[3];
  unsigned char char_array_4[4];

  while (in_len--) {
    char_array_3[i++] = *(bytes_to_encode++);
    if (i == 3) {
      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
      char_array_4[3] = char_array_3[2] & 0x3f;

      for(i = 0; (i <4) ; i++)
        ret += base64_chars[char_array_4[i]];
      i = 0;
    }
  }

  if (i)
  {
    for(j = i; j < 3; j++)
      char_array_3[j] = '\0';

    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
    char_array_4[3] = char_array_3[2] & 0x3f;

    for (j = 0; (j < i + 1); j++)
      ret += base64_chars[char_array_4[j]];

    while((i++ < 3))
      ret += '=';

  }

  return ret;

}

std::string base64_decode(std::string const& encoded_string) {
  int in_len = encoded_string.size();
  int i = 0;
  int j = 0;
  int in_ = 0;
  unsigned char char_array_4[4], char_array_3[3];
  std::string ret;

  while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
    char_array_4[i++] = encoded_string[in_]; in_++;
    if (i ==4) {
      for (i = 0; i <4; i++)
        char_array_4[i] = base64_chars.find(char_array_4[i]);

      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

      for (i = 0; (i < 3); i++)
        ret += char_array_3[i];
      i = 0;
    }
  }

  if (i) {
    for (j = i; j <4; j++)
      char_array_4[j] = 0;

    for (j = 0; j <4; j++)
      char_array_4[j] = base64_chars.find(char_array_4[j]);

    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

    for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
  }

  return ret;
}

Packet packet4xmlnode( xmlnode *packet_xml )
{
	Packet result;
	const char *xmlns = xmlnode_get_namespace(packet_xml);

	result.sid = "";
	result.from = "pidgin";
	result.type = "";
	result.data = "";


	if( !strcmp( packet_xml->name, "wb") && !strcmp(xmlns,"http://inkscape.org/inkboard") ) {
		const char *session_id;
		session_id  = xmlnode_get_attrib( packet_xml, "session");
		if( session_id != NULL ){
			result.sid = session_id;
		}

		xmlnode *packet_xml2 = xmlnode_get_child( packet_xml, "protocol" );
		if ( packet_xml2 != NULL ){
			result.type = PROTOCOL;

			xmlnode *packet_xml3 = packet_xml2->child;
			if( packet_xml3 != NULL ){
				if ( packet_xml3->name == INVITE ){
					result.data = "invite";
				}
				else if ( packet_xml3->name == ACCEPT ){
					result.data = "accept";
				}
				else if ( packet_xml3->name == DENY ){
					result.data = "deny";
				}
				else if ( packet_xml3->name == OK ){
					result.data = "ok";
				}
				else if ( packet_xml3->name != NULL ){
					result.data = packet_xml3->name;
				}
				else {
					result.data = "none";
				}
			}
		}
		else {
			packet_xml2 = xmlnode_get_child( packet_xml, "sync" );
			if ( packet_xml2 != NULL ){
				result.type = SYNC;
				result.data = xmlnode_get_data( packet_xml2 ); 
			}
			else{
				result.type = MESSAGE;
				result.data = xmlnode_get_data( packet_xml ); 
			}
		}

	}

	return result;
}

void showPacket( const Packet& pkt )
{
	cerr << "=== packet info {{{" << endl;
	cerr << "sid: "  <<   pkt.sid   << endl;      
	cerr << "from: " <<   pkt.from  << endl;       
	cerr << "type: " <<   pkt.type  << endl;       
	cerr << "data: " <<   pkt.data  << endl;       
	cerr << "}}} packet info ===" << endl;
}


/*
 * Implementation of select contact dialog
 */
struct Args {
	vector<PurpleBuddy*> candidates; 
	string app; 
        PurpleBuddy *buddy;
	void(*func)(string, PurpleBuddy* );
};


void combo_change( gpointer inst, gpointer data ){
	GtkComboBox* combo = GTK_COMBO_BOX( inst );

	Args *args = (Args*)data;	
	vector<PurpleBuddy*> candidates = args->candidates;
        PurpleBuddy *buddy;
	
	int index = gtk_combo_box_get_active( combo );
        args->buddy = candidates[index];
}

void cancel( gpointer inst, gpointer data ){
	g_signal_emit_by_name( gtk_widget_get_toplevel(GTK_WIDGET(inst)), "destroy");
}

void confirm( gpointer inst, gpointer data ){

	Args *args = (Args*)data;	
	string app = args->app;
        PurpleBuddy *buddy = args->buddy;
	void(*func)(string, PurpleBuddy* ) = args->func;

        if ( buddy != NULL ){
            (*func)( app, buddy );
        }

	g_signal_emit_by_name( gtk_widget_get_toplevel(GTK_WIDGET(inst)), "destroy");
}

void destroy( gpointer inst, gpointer data ){
	// do clean residual variables
	gtk_widget_destroy( GTK_WIDGET(inst) );
}

gboolean showSelectContactDialogCallback( gpointer data )
{
	Args *args = (Args*)data;	
	vector<PurpleBuddy*> candidates = args->candidates;
	string app = args->app;
        PurpleBuddy *buddy = args->buddy;
	void(*func)(string, PurpleBuddy* ) = args->func;

	gdk_threads_init (); 
	gdk_threads_enter();
	gtk_init(0, 0);

	/* 建立視窗元件 */
	GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_title(GTK_WINDOW(window), "contact select");
	gtk_window_set_default_size(GTK_WINDOW(window), 200, 50);

	/* 建立 GUI 元件 */
	GtkWidget *combo = gtk_combo_box_new_text();
	GtkWidget *button20;                                                                        
	GtkWidget *button21;
	button20 = gtk_button_new_with_label("Cancel");
	button21 = gtk_button_new_with_label("Confirm");
	
	/* 設定選單內容 */
	for( int i=0 ; i < candidates.size(); i++ ){
		string item;
		PurpleBuddy *buddy;
		PurpleAccount *acct;
		string buddy_name;
		string acct_name;

		buddy = candidates[i];
		acct = purple_buddy_get_account( buddy );

		buddy_name = purple_buddy_get_name( buddy );
		acct_name = purple_account_get_username( acct );

		item = buddy_name+" ( "+acct_name+")";

		gtk_combo_box_append_text ( GTK_COMBO_BOX(combo), item.c_str() );
		cerr << "adding... "  << item << endl;
	}
        // set default candidate.
	gtk_combo_box_set_active( GTK_COMBO_BOX(combo), 0 );
        args->buddy = candidates[0];
        


	/* GUI 元件的排版 */
	GtkWidget *vbox;
	GtkWidget *hbox1;
	GtkWidget *hbox3;

	vbox = gtk_vbox_new(TRUE, 5);
	hbox1 = gtk_hbox_new(TRUE, 5);
	hbox3 = gtk_hbox_new(TRUE, 5);

	gtk_box_pack_start(GTK_BOX(vbox), hbox1, TRUE, TRUE, 5);
	gtk_box_pack_start(GTK_BOX(vbox), hbox3, TRUE, TRUE, 5);

	gtk_box_pack_start(GTK_BOX(hbox1), GTK_WIDGET(combo), TRUE, TRUE, 5);

	gtk_box_pack_start(GTK_BOX(hbox3), GTK_WIDGET(button20), TRUE, TRUE, 5);
	gtk_box_pack_start(GTK_BOX(hbox3), GTK_WIDGET(button21), TRUE, TRUE, 5);

	gtk_container_add(GTK_CONTAINER(window), vbox);

	/* 建立事件與處理函式的連繫 */
	g_signal_connect(GTK_OBJECT(button20), "clicked", G_CALLBACK(cancel), NULL );
	g_signal_connect(GTK_OBJECT(button21), "clicked", G_CALLBACK(confirm), args );
	g_signal_connect(GTK_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL);
	g_signal_connect(G_OBJECT (GTK_COMBO_BOX (combo) ), "changed", G_CALLBACK (combo_change), args );

	/* 顯示 window 出來 */
	gtk_widget_show_all(window);
	gdk_threads_leave();

        return FALSE;
}

void showSelectContactDialog( const vector<PurpleBuddy*> &candidates, string app, void(*func)(string, PurpleBuddy* ) )
{
	Args* arg_prt = new Args;
	arg_prt->candidates = candidates;
	arg_prt->app = app;
	arg_prt->func = func;

        g_idle_add( (GSourceFunc)showSelectContactDialogCallback, arg_prt );
}



void pidginSend( PurpleBuddy *buddy, string serial, string text, string app ){

	PurpleAccount *acct = NULL;
	PurplePluginProtocolInfo *prpl_info = NULL;
	PurpleConnection *gc = NULL;
	string from;
	string to;
	string type;
	string content;
	string stanza;

	if ( buddy == NULL ) { return ; }

	acct = purple_buddy_get_account( buddy );
        gc = purple_account_get_connection( acct );
	to = purple_buddy_get_name( buddy );
	from = string( purple_account_get_username( acct ) );
	type = "normal";
	content = base64_encode( (unsigned char*)text.c_str(), text.size() );
	stanza = "<message type='"+type+"' to='"+to+"'><inkboard id='"+serial+"' app='"+app+"' >"+content+"</inkboard></message>";

	if (gc) {
		prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl);
		if (prpl_info && prpl_info->send_raw != NULL) {
			std::cerr << "[xmpp]:" << std::endl << stanza << std::endl;
			prpl_info->send_raw(gc, stanza.c_str(), stanza.size() );
			std::cerr << "send done" << std::endl;
		}
	}
}

bool isJIDsimilar( string left, string right )
{
	bool result=false;

	size_t foundL = left.find(right);
	size_t foundR = right.find(left);

	if ( foundL != string::npos || foundR!= string::npos ){
		result = true;	
	}

	return result;
}

string randomString()
{
	string result;

	for ( int n=0; n < 16; n++ ) {
		result += rand()%26+'a';
	}

	return result;
}
